﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

using Palsta.Core.data;
using Palsta.Core.exp;

namespace Palsta.Core.parser
{
    class ExpressionParser
    {
                
        #region Members

        private String exp;
        private String context;
        private String str;
        private static Regex rgxDirectValue = null;
        private static Regex rgxRefValue = null;
        private static Regex rgxFunctionName = null;
        private static Regex rgxOperator = null;
        private static Regex rgxLogical = null;

        #endregion

        #region Constructors

        public ExpressionParser(String exp)
            : this(exp, "")
        {
        }

        public ExpressionParser(String exp, String context)
        {
            this.exp = exp;
            this.context = context;
            if (rgxDirectValue == null)
            {
                rgxDirectValue = new Regex("^\\\"([^\\\"]*)\\\"|^[0-9][0-9]*");
            }
            if (rgxRefValue == null)
            {
                rgxRefValue = new Regex("^(\\w|\\:|\\/|_|\\-|\\.)*");
            }
            if (rgxFunctionName == null)
            {
                rgxFunctionName = new Regex("^([a-zA-Z\\:])+\\(");
            }
            if (rgxOperator == null)
            {
                rgxOperator = new Regex("\\=|\\>|\\>\\=|\\<\\=|\\!\\=");
            }
            if (rgxLogical == null)
            {
                rgxLogical = new Regex("^and|^or");
            }
        }

        #endregion

        #region Accessors

        #endregion

        #region Methods

        public Expression Parse()
        {
            this.str = exp.Trim();

            ExpNode n = this.ParseSingleExp();
            while (!this.Finish())
            {
                ExpLogicalNode logicalNode = this.ParseLogicalNode();
                if (logicalNode == null)
                {
                    if (this.Finish())
                    {
                        break;
                    }
                    else
                    {
                        return null;
                    }
                }

                ExpNode rn = this.ParseSingleExp();
                if (rn == null)
                {
                    return null;
                }

                logicalNode.LNode = n;
                logicalNode.RNode = rn;
                n = logicalNode;
            }
            Expression ipe = new Expression();
            ipe.SetRootNode(n);
            return ipe;
        }

        private ExpNode ParseSingleExp()
        {
            ExpNode lv = this.ParseOperand();
            if (lv == null)
            {
                return null;
            }
            ExpOpNode op = this.ParseOperator();
            if (op == null)
            {
                if (this.Finish())
                {
                    return lv;
                }
                else
                {
                    return null;
                }
            }
            ExpNode rv = this.ParseOperand();
            if (rv == null) return null;
            op.SetOperands(lv, rv);
            return op;
        }

        private ExpNode ParseOperand()
        {
            ExpNode n = null;
            String tk = this.GetToken(rgxDirectValue);
            if (tk != null)
            {
                String v = tk.Substring(1, tk.Length - 2);
                n = new ExpValueNode(v);
                return n;
            }

            tk = this.GetToken(rgxFunctionName);
            if (tk != null)
            {
                ExpFunctionNode fn = new ExpFunctionNode();
                fn.SetName(tk.Substring(0, tk.Length - 1).Replace(':','_'));
                ExpNode arg = null;

                while (!this.GetChar(')'))
                {
                    arg = this.ParseOperand();
                    if (arg == null)
                    {
                        break;
                    }

                    fn.AddArg(arg);
                    if (!this.GetChar(','))
                    {
                        if (!this.GetChar(')')) 
                        {
                            return null;
                        }
                        break;
                    }
                }
                return fn;

            }


            tk = this.GetToken(rgxRefValue);
            if (tk != null)
            {
                String absoluteName = this.context;
                String relativeName = tk;
                while (relativeName.Length > 0 && relativeName.StartsWith("."))
                {
                    if (relativeName.StartsWith(".."))
                    {
                        int end = absoluteName.LastIndexOf('/');
                        if (end == -1)
                        {
                            return null;
                        }
                        absoluteName = absoluteName.Substring(0, end);
                        if (relativeName.Length > 2)
                        {
                            relativeName = relativeName.Substring(3);
                        }
                        else
                        {
                            relativeName = "";
                        }
                    }
                    else
                    {
                        relativeName = "";
                    }
                }
                if (relativeName.Length > 0)
                {
                    absoluteName = absoluteName + "/" + relativeName;
                }
                n = new ExpRefNode(absoluteName);
                return n;
            }
            return null;
        }

        private ExpOpNode ParseOperator()
        {
            String op = this.GetToken(rgxOperator);
            if (op == null)
            {
                return null;
            }
            ExpOpNode n = new ExpOpNode();
            if (op.Equals("="))
            {
                n.SetOp(ExpOpType.Equels);
            }
            else if (op.Equals(">"))
            {
                n.SetOp(ExpOpType.GreaterThan);
            }
            else if (op.Equals("<"))
            {
                n.SetOp(ExpOpType.LessThan);
            }
            else if (op.Equals(">="))
            {
                n.SetOp(ExpOpType.NotLessThan);
            }
            else if (op.Equals("<="))
            {
                n.SetOp(ExpOpType.NotGreaterThan);
            }
            else if (op.Equals("!="))
            {
                n.SetOp(ExpOpType.NotEquels);
            }
            else
            {
                return null;
            }

            return n;
        }

        private ExpLogicalNode ParseLogicalNode()
        {
            String op = this.GetToken(rgxLogical);
            if (op == null)
            {
                return null;
            }
            ExpLogicalNode n = new ExpLogicalNode();
            if (op.ToLower().Equals("and"))
            {
                n.OP = ExpLogicalNode.ExpLogicalType.And;
            }
            else if (op.ToLower().Equals("or"))
            {
                n.OP = ExpLogicalNode.ExpLogicalType.Or;
            }
            else
            {
                return null;
            }

            return n;
        }


        private bool Finish()
        {
            return (this.str.Length == 0);
        }

        private String GetToken(Regex rgx)
        {
            Match m = rgx.Match(this.str);
            if (m.Success)
            {
                String v = this.str.Substring(m.Index, m.Length);
                this.str = this.str.Substring(m.Length).TrimStart();
                return v;
            }
            return null;
        }

        private bool GetChar(char c)
        {
            if (this.str[0] == c)
            {
                this.str = this.str.Substring(1).TrimStart();
                return true;
            }
            return false;
        }
        #endregion

        #region Override Operators

        #endregion

        #region Static Methods

        #endregion
    }
}
